package br.edu.ufcg.lsd.wbis.control.user;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import br.edu.ufcg.lsd.wbis.control.equipment.EquipmentManagerListener;
import br.edu.ufcg.lsd.wbis.control.exception.CannotOpenDatabaseSessionException;
import br.edu.ufcg.lsd.wbis.control.exception.DuplicatedIdentifierException;
import br.edu.ufcg.lsd.wbis.control.exception.UnknownEntityException;
import br.edu.ufcg.lsd.wbis.control.util.SecurityUtil;
import br.edu.ufcg.lsd.wbis.control.util.matching.Matcher;

/**
 * This is the manager for dealing with user entities. All methods regarding
 * dealing with users is depicted here.
 * 
 * @author Renato Miceli
 * @see EquipmentManagerListener
 */
public class UserManager implements EquipmentManagerListener {

	// FIXME: all methods return the bare user contained at the map. Critical
	// info returned that can be changed without any checking or authentication
	// (the info stored at the database is safe, though).
	// FIXME: critical info is being returned (password hash).

	/**
	 * Map for storing the users by their logins.
	 */
	private final Map<String, User> users;

	/**
	 * DAO for database access.
	 */
	private final UserDAO userDAO;

	/**
	 * List of this manager's listeners.
	 */
	private final List<UserManagerListener> listeners;

	/**
	 * Builds a new User Manager.
	 * 
	 * @throws CannotOpenDatabaseSessionException
	 */
	public UserManager() throws CannotOpenDatabaseSessionException {
		this(new HibernateUserDAO());
	}

	/**
	 * Builds a new User Manager with the given DAO. <br>
	 * Note: for testing purposes only!
	 * 
	 * @param dao
	 *            this User Manager's DAO
	 */
	protected UserManager(UserDAO dao) {

		this.userDAO = dao;
		this.users = new TreeMap<String, User>();
		this.listeners = new LinkedList<UserManagerListener>();
		loadData();
	}

	/**
	 * Loads the persisted data.
	 */
	private void loadData() {
		for (User user : this.userDAO.getUsers()) {
			this.users.put(user.getLogin(), user);
		}
	}

	public void addListener(UserManagerListener listener) {
		listeners.add(listener);
	}

	/**
	 * Returns all the users stored by this manager. Note that the returned list
	 * is mutable, but the changes do not reflect on the stored users.
	 * 
	 * @return all the users stored by this manager
	 */
	public List<User> getUsers() {
		return new LinkedList<User>(this.users.values());
	}

	/**
	 * Adds a new user to this manager.
	 * 
	 * @param login
	 *            the user's login
	 * @param password
	 *            the user's password
	 * @param name
	 *            the user's name
	 * @param email
	 *            the user's email
	 * @param level
	 *            the user's level
	 * @return the newly registered user
	 * @throws DuplicatedIdentifierException
	 *             in case another user with the same login is already
	 *             registered to this manager
	 */
	public User addUser(String login, String password, String name, String email, boolean authorized, UserLevel level) throws DuplicatedIdentifierException {

		if (this.users.containsKey(login)) {
			throw new DuplicatedIdentifierException(login);
		}

		String hashedPswd = SecurityUtil.generateHash(password);
		User newUser = new User(name, email, login, hashedPswd, authorized, level);

		this.userDAO.addUser(newUser);

		this.users.put(login, newUser);
		return newUser;
	}

	/**
	 * Returns the user given by the pointed login.
	 * 
	 * @param login
	 *            the user's login
	 * @return the user given by the pointed login
	 * @throws UnknownEntityException
	 *             in case no user is registered with the given login
	 */
	public User getUser(String login) throws UnknownEntityException {

		if (!this.users.containsKey(login)) {
			throw new UnknownEntityException(login);
		}

		return this.users.get(login);
	}

	/**
	 * Removes a user registered to this manager. The removed user has the same
	 * login as the given one.
	 * 
	 * @param login
	 *            the login of the user to be removed.
	 * @throws UnknownEntityException
	 *             if no user with the given login is registered to this manager
	 */
	public void removeUser(String login) throws UnknownEntityException {

		User user = this.getUser(login);

		for (UserManagerListener listener : listeners) {
			listener.userRemoved(user);
		}

		this.userDAO.removeUser(user);

		this.users.remove(login);
	}

	/**
	 * Updates a user by pointing the new values for its fields. The user to be
	 * updated is uniquely identified by the given login.
	 * 
	 * @param login
	 *            the user's login
	 * @param newName
	 *            the user's new name
	 * @param newEmail
	 *            the user's new email
	 * @param newLevel
	 *            the user's new level
	 * @param authorizationStatus
	 *            the user's new authorization status
	 * @throws UnknownEntityException
	 *             in case no user with the given login is registered to this
	 *             manager
	 */
	public void updateUserInfo(String login, String newName, String newEmail, UserLevel newLevel, boolean authorizationStatus)
			throws UnknownEntityException {

		User user = this.getUser(login);
		user.setName(newName);
		user.setEmail(newEmail);
		user.setLevel(newLevel);
		user.setAuthorized(authorizationStatus);
		
		this.userDAO.updateUser(user);
	}

	/**
	 * Searches the set of all users registered to this manager and returns a
	 * list of the users filtered according to the given user matcher. All users
	 * in the returned list must match the given user matcher, and no other
	 * registered user matches it.
	 * 
	 * @param userMatcher
	 *            the filtering user matcher.
	 * @return a list of users filtered according to the given user matcher
	 */
	public List<User> searchUsers(Matcher<User> userMatcher) {
		List<User> result = new ArrayList<User>(this.users.size());
		for (User user : this.users.values()) {
			if (userMatcher.matches(user)) {
				result.add(user);
			}
		}
		return result;
	}

	/**
	 * @param myLogin
	 * @param myPwd
	 * @param login
	 * @param authorize
	 * @throws UnknownEntityException
	 */
	public void authorizeUser(String login, boolean authorize) throws UnknownEntityException {

		User user = this.getUser(login);
		user.setAuthorized(authorize);
		this.userDAO.updateUser(user);
	}

	/**
	 * @param loginToUpdate
	 * @param newPassword
	 * @throws UnknownEntityException
	 */
	public void updateUserPassword(String login, String password) throws UnknownEntityException {

		String hashedPswd = SecurityUtil.generateHash(password);

		User user = this.getUser(login);
		user.setPassword(hashedPswd);

		this.userDAO.updateUser(user);
	}

}
